﻿
import *as uuid from 'uuid'
import *as redis from 'ioredis'
import *as mysql from 'mysql'
import { 登录账号, 系统权限 } from './内置服务/数据类型'
import { 加解密 } from './工具/加解密'
import { Redis数据库服务 } from './内置服务/Redis数据库/Redis数据库服务'

export class 登录服务 {
    public 登录帮助: Redis数据库服务
    public 本机登录记录: Object = {}
    public 等待第三方登录: Object = {}
    private 订阅: redis.Redis
    private 发布: redis.Redis
    private 时间表 = {
        天: 60 * 1000 * 60 * 24,
        小时: 60 * 1000 * 60,
        分: 60 * 1000,
        秒: 1000
    }
    constructor(Redis数据库: Redis数据库服务, 订阅: redis.Redis, 发布: redis.Redis) {
        this.登录帮助 = Redis数据库
        this.订阅 = 订阅
        this.发布 = 发布
    }
    public 登录 = async (套接字: SocketIO.Socket, 错误处理: (err?: any) => void) => {
        try {
            //
            //根据 累计时长 计算账号 等级
            //储存账号等级
            //
            let 账号: 登录账号 = {}
            账号.私匙 = 套接字.handshake.query['私匙'] || null
            let 第三方数据 = 套接字.handshake.query['第三方数据'] || null
            第三方数据 = JSON.parse(第三方数据)
            let 地点 = JSON.stringify(套接字.request.connection._peername)
            if (账号.私匙 && !(账号.账号 && 账号.密码)) {
                let 应用id = await this.登录帮助.应用私匙储存.同步键值读值(账号.私匙)
                if (!应用id) {
                    套接字.emit('error', '私匙不正确')
                }
                账号.应用 = 应用id
                let 文本: string = await this.登录帮助.业务储存.同步键值读值(应用id + '_' + '第三方登录')
                let 业务办理者数组: Array<string> = 文本.replace(new RegExp('^\\|?', 'g'), '').split('|')
                let 业务办理员数量 = 业务办理者数组.length || 0
                if (业务办理员数量 == 0) {
                    套接字.emit('error', '没有这个业务')
                }
                let 余数 = parseInt(套接字.id.slice(0, 8), 16) % 业务办理员数量
                let 业务办理者 = 业务办理者数组[余数]
                if (!业务办理者) {
                    套接字.emit('error', '这个业务没有办理者')
                }
                let 时间 = Date.now()
                let Id = uuid()
                账号.账号 = 套接字.id
                let 自定义事件 = 套接字.handshake.query['自定义事件'] || null

                第三方数据 = { ...第三方数据, 地点: 地点, 私匙: 账号.私匙 }

                let 合并数据 = { id: Id, 自定义事件: 自定义事件, 数据: 第三方数据, 申请账号: 账号.账号, 事件: '第三方登录', 接收时间: 时间 }
                let 办理业务的数据 = JSON.stringify(合并数据)
                if (!办理业务的数据) {
                    套接字.emit('error', '发送的数据包含错误')
                }
                this.登录帮助.消息接收储存.set(Id, 办理业务的数据)
                账号.权限 = 账号.权限 | 系统权限.办理业务
                this.等待第三方登录[账号.账号] = 套接字
                套接字['账号'] = 账号
                套接字.join(账号.账号)
                this.订阅.subscribe(账号.账号)
                this.发布.publish(业务办理者, 办理业务的数据, (err) => {
                    if (err) {
                        delete this.等待第三方登录[账号.账号]
                        套接字.leave(账号.账号)
                        套接字.emit('error', JSON.stringify(err))
                    }
                    错误处理()
                })
                return
            }
            账号.账号 = 套接字.handshake.query['账号'] || null
            账号.密码 = 套接字.handshake.query['密码'] || null
            let 数据验证 = this.验证账号(账号)
            if (数据验证) {
                let 加密密码 = 加解密.不可逆加密(账号.账号 + 账号.密码)
                let 是否存在 = await this.登录帮助.登录账号储存.同步检查键(账号.账号)
                if (是否存在 == 0) {
                    套接字.emit('error', '账号不存在')
                }
                let 结果 = await this.登录帮助.登录账号储存.同步哈希表读多字段(账号.账号, '密码', '权限', '登录时间', '密码错误记录',
                    '状态', '离线消息')
                let 库内密码 = 结果[0] || null
                let 库内权限 = parseInt(结果[1]) || null
                let 库内登录时间 = parseInt(结果[2]) || null
                let 库内错误记录 = JSON.parse(结果[3]) || null
                let 库内状态: 库内状态 = JSON.parse(结果[4]) || {}
                let 库内离线消息 = 结果[5] || null
                if (库内错误记录) {
                    let 锁死时间 = 库内错误记录.锁死时间
                    let 现在 = Date.now()
                    let 登录时间 = 库内错误记录.登录时间
                    if ((现在 - 登录时间) < 锁死时间) {
                        let 时间 = (锁死时间 - (现在 - 登录时间))
                        let 返回时间 = this.格式化时间(时间)
                        套接字.emit('error', `账号处于锁死状态,请于 ${返回时间} 后,再次登录`)
                    }
                }
                if (库内登录时间) {
                    let 时间 = Date.now() - 库内登录时间
                    if (时间 < 1000) {
                        套接字.emit('error', '登录太过频繁,账号异常.')
                    }
                }
                if (库内密码 != 加密密码) {
                    if (库内错误记录) {
                        let 现在 = Date.now()
                        let 记录 = 库内错误记录
                        if (记录.锁死时间 == -1) {
                            套接字.emit('error', '账号永久锁死,请使用密保找回密码或联系管理员.')
                        }
                        记录.错误次数 = 记录.错误次数 || 2
                        if (记录.错误次数 > 9) {
                            记录.锁死时间 = -1
                            套接字.emit('error', `账号${记录.错误次数}次输入错误,账号将永久锁死,请使用密保找回密码或联系管理员.`)
                        }
                        let 错误消息: string
                        if (现在 - 记录.登录时间 < (60 * 1000) * 记录.错误次数 * 记录.错误次数) {
                            记录.锁死时间 = (60 * 1000) * 记录.错误次数 * 记录.错误次数
                            let 锁死时间 = this.格式化时间(记录.锁死时间)
                            错误消息 = `账号${记录.错误次数}次输入错误,锁死${锁死时间},请稍后再试.`
                        }
                        记录.错误次数++
                        let 保存记录 = {
                            错误次数: 记录.错误次数,
                            锁死时间: 记录.锁死时间,
                            登录时间: 现在
                        }
                        await this.登录帮助.登录账号储存.同步哈希表写入指定字段值(账号.账号, '密码错误记录', JSON.stringify(保存记录))
                        if (错误消息) {
                            套接字.emit('error', 错误消息)
                        }
                    } else {
                        let 保存记录 = {
                            错误次数: 1,
                            锁死时间: 0,
                            登录时间: Date.now()
                        }
                        await this.登录帮助.登录账号储存.同步哈希表写入指定字段值(账号.账号, '密码错误记录', JSON.stringify(保存记录))
                    }
                    套接字.emit('error', '账号或密码错误.')
                }
                if (库内权限) {
                    if (系统权限.登录权限 == (库内权限 & 系统权限.登录权限)) {
                        let 时间 = Date.now()
                        库内状态[套接字.id] = { 登录时间: 时间, 登录地点: 地点 }
                        if (库内离线消息) {
                            套接字.emit('离线消息', 库内离线消息)
                        }
                        this.登录帮助.登录账号储存.同步哈希表储存多字段(账号.账号, '状态', JSON.stringify(库内状态),
                            '登录时间', 时间.toString(), '离线消息', '')
                        账号.已登录 = '正常登录'
                        账号.权限 = 库内权限
                    }
                } else {
                    账号.账号 = 套接字.id
                    账号.已登录 = '游客登录'
                    账号.权限 = 系统权限.注册权限
                }
            } else {
                账号.账号 = 套接字.id
                账号.已登录 = '游客登录'
                账号.权限 = 系统权限.注册权限
            }
            await this.登录帮助.登录账号储存.同步哈希表删除字段值(账号.账号, '密码错误记录')
            this.本机登录记录[账号.账号] = 套接字
            套接字['账号'] = 账号
            套接字.join(账号.账号)
            this.订阅.subscribe(账号.账号)
            错误处理()
        } catch (err) {
            错误处理(err)
        }
    }
    public 退出 = async (套接字: SocketIO.Socket) => {
        let 账号: 登录账号 = 套接字['账号']
        if (账号.已登录 == '游客登录') {
            delete this.本机登录记录[账号.账号]
        } else if (账号.已登录 == '第三方登录') {
            this.登录帮助.第三方登录.同步集合删除成员(账号.应用, 账号.账号)
            delete this.本机登录记录[账号.账号]

            let 文本: string = await this.登录帮助.业务储存.同步键值读值(账号.应用 + '_' + '第三方下线')
            let 业务办理者数组: Array<string> = 文本.replace(new RegExp('^\\|?', 'g'), '').split('|')

            let 业务办理员数量 = 业务办理者数组.length || 0
            if (业务办理员数量 == 0) {
                套接字.emit('error', '没有这个业务')
            }
            let 余数 = parseInt(套接字.id.slice(0, 8), 16) % 业务办理员数量
            let 业务办理者 = 业务办理者数组[余数]
            if (!业务办理者) {
                套接字.emit('error', '这个业务没有办理者')
            }
            let 时间 = Date.now()
            let Id = uuid()

            let 合并数据 = { id: Id, 自定义事件: '', 数据: '', 申请账号: 账号.账号, 事件: '第三方下线', 接收时间: 时间 }
            let 办理业务的数据 = JSON.stringify(合并数据)
            if (!办理业务的数据) {
                套接字.emit('error', '发送的数据包含错误')
            }
            this.登录帮助.消息接收储存.set(Id, 办理业务的数据)
            this.发布.publish(业务办理者, 办理业务的数据)
        } else {
            this.本机登录记录[账号.账号] = null
            let 库内储存 = await this.登录帮助.登录账号储存.同步哈希表读多字段(账号.账号, '状态', '登录时间', '累计时长')
            let 状态: 库内状态 = JSON.parse(库内储存[0]) || null
            let 时长 = Date.now() - parseInt(库内储存[1])
            if (库内储存[2]) {
                时长 += parseInt(库内储存[2])
            }
            delete 状态[套接字.id]
            this.登录帮助.登录账号储存.同步哈希表储存多字段(账号.账号, '状态', JSON.stringify(状态), '累计时长', 时长.toString())
        }
        套接字.leaveAll()
        this.订阅.unsubscribe(账号.账号)
    }
    private 验证账号(账号: 登录账号) {
        let 号码 = 账号.账号 || null
        let 密码 = 账号.密码 || null
        if (号码 == null && 密码 == null) {
            return false
        }
        let 有号码 = /^[\d]{8,12}$/g.test(号码)
        let 有密码 = /^[\w\S]{6,10}$/g.test(密码)
        return 有号码 && 有密码
    }
    private 格式化时间(时间: number) {
        let 返回 = ''
        for (let k in this.时间表) {
            if (时间 > this.时间表[k]) {
                返回 += Math.floor(时间 / this.时间表[k]).toString() + k
                时间 = 时间 % this.时间表[k]
            } else if (k == '秒' && 时间 < this.时间表[k]) {
                返回 += '钟'
            }
        }
        return 返回
    }
}

interface 库内状态 {
    [id: string]: {
        登录时间: number,
        登录地点: string
    }
}